/**
 * 
 */
package jmine.tec.utils.collection;

import java.io.Serializable;
import java.util.Collection;

/**
 * Classe utilitaria
 * 
 * @author takeshi
 */
public final class PredicateUtils {

    /**
     * Nao pode ser instanciada
     */
    private PredicateUtils() {
    }

    /**
     * Finds the fist element that matches de predicate, null if not found.
     * 
     * @param <E> element type
     * @param collection collection
     * @param filter filter
     * @return matching element
     */
    public static <E> E find(final Collection<? extends E> collection, final Predicate<? super E> filter) {
        for (E e : collection) {
            if (filter.matches(e)) {
                return e;
            }
        }
        return null;
    }

    /**
     * @param <E> the filter type
     * @param collection the collection
     * @param filter the predicate to be used as a filter
     * @return Collection
     */
    public static <E> Collection<E> filterView(final Collection<? extends E> collection, final Predicate<? super E> filter) {
        return new FilteredCollection<E>(collection, filter);
    }

    /**
     * Devolve um predicado que eh uma conjuncao ( && ) dos predicados, onde o valor padrao é true
     * 
     * @param <E> o tipo do predicado
     * @param preds os predicados
     * @return {@link Predicate}
     */
    public static <E> Predicate<E> conjunctionPredicate(final Predicate<E>... preds) {
        switch (preds.length) {
        case 0:
            return truePredicate();

        case 1:
            return preds[0];

        default:
            return new ConjunctionPredicate<E>(preds);
        }
    }

    /**
     * Devolve um predicado que eh uma conjuncao ( && ) dos predicados, onde o valor padrao é true
     * 
     * @param <E> e
     * @param left Predicate
     * @param right Predicate
     * @return Predicate
     */
    public static <E> Predicate<E> and(final Predicate<E> left, final Predicate<E> right) {
        if (left == falsePredicate() || right == falsePredicate()) {
            return falsePredicate();
        }
        if (right == truePredicate()) {
            return left;
        }
        if (left == truePredicate()) {
            return right;
        }
        return new Predicate<E>() {

            public boolean matches(E e) {
                return left.matches(e) && right.matches(e);
            }
        };
    }

    /**
     * Devolve uma disjuncao ( || ) entre os predicados
     * 
     * @param <E> o tipo
     * @param left Predicate
     * @param right Predicate
     * @return {@link Predicate}
     */
    public static <E> Predicate<E> or(final Predicate<E> left, final Predicate<E> right) {
        if (left == truePredicate() || right == truePredicate()) {
            return truePredicate();
        }
        if (left == falsePredicate()) {
            return right;
        }
        if (right == falsePredicate()) {
            return left;
        }
        return new Predicate<E>() {

            public boolean matches(E e) {
                return left.matches(e) || right.matches(e);
            }
        };
    }

    /**
     * Devolve uma disjuncao ( || ) entre os predicados
     * 
     * @param <E> o tipo
     * @param preds os predicados
     * @return {@link Predicate}
     */
    public static <E> Predicate<E> disjunctionPredicate(final Predicate<E>... preds) {
        switch (preds.length) {
        case 0:
            return falsePredicate();

        case 1:
            return preds[0];

        default:
            return new DisjunctionPredicate<E>(preds);
        }
    }

    /**
     * Devolve um predicado que sempre devolve <code>true</code>
     * 
     * @param <E> o tipo
     * @return {@link Predicate}
     */
    @SuppressWarnings("unchecked")
    public static <E> Predicate<E> truePredicate() {
        return ConstantPredicate.TRUE_PREDICATE;
    }

    /**
     * Devolve um predicado que devolve sempre <code>false</code>
     * 
     * @param <E> o tipo do predicado
     * @return {@link Predicate}
     */
    @SuppressWarnings("unchecked")
    public static <E> Predicate<E> falsePredicate() {
        return ConstantPredicate.FALSE_PREDICATE;
    }

    /**
     * @author MarceloT
     */
    public static final class ConstantPredicate implements Predicate, Serializable {
        /**
         * 
         */
        private static final long serialVersionUID = -5127522733109735675L;

        private final boolean value;

        public static final ConstantPredicate TRUE_PREDICATE = new ConstantPredicate(true), FALSE_PREDICATE = new ConstantPredicate(false);

        /**
         * @param value the value
         */
        private ConstantPredicate(final boolean value) {
            super();
            this.value = value;
        }

        /**
         * @param e e
         * @return value
         */
        public boolean matches(final Object e) {
            return this.value;
        }

        /**
         * @return Object
         */
        private Object readResolve() {
            if (this.value) {
                return TRUE_PREDICATE;
            }
            return FALSE_PREDICATE;
        }

    }

    /**
     * @param <E> o tipo do Predicate
     * @param excludePatterns Predicate
     * @return Predicate
     */
    public static <E> Predicate<E> inverse(final Predicate<E> excludePatterns) {
        return new Predicate<E>() {

            public boolean matches(E e) {
                return !excludePatterns.matches(e);
            }
        };
    }

}
